<template>
  <div class="upload-box">
    <el-upload
      :file-list="_fileList"
      :action="uploadImgUrl"
      list-type="picture-card"
      :before-upload="beforeUpload"
      :on-success="uploadSuccess"
      :on-exceed="handleExceed"
      :on-error="uploadError"
      :class="['upload', self_disabled ? 'disabled' : '', drag ? 'no-border' : '', { hide: _fileList.length >= limit }]"
      :multiple="true"
      :headers="headers"
      :disabled="self_disabled"
      :limit="limit"
      :drag="drag"
      :accept="fileType.join(',')"
    >
      <div class="upload-empty">
        <slot name="empty">
          <el-icon><Plus /></el-icon>
          <!-- <span>请上传图片</span> -->
        </slot>
      </div>
      <template #file="{ file }">
        <img :src="file.url" class="upload-image" />
        <div class="upload-handle" @click.stop>
          <div class="handle-icon" @click="handlePictureCardPreview(file)">
            <el-icon><ZoomIn /></el-icon>
            <span>查看</span>
          </div>
          <div v-if="!self_disabled" class="handle-icon" @click="handleRemove(file)">
            <el-icon><Delete /></el-icon>
            <span>删除</span>
          </div>
        </div>
      </template>
    </el-upload>
    <div v-if="showTip" class="el-upload__tip">
      请上传
      <template v-if="fileSize">
        大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
      </template>
      <template v-if="fileType">
        格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
      </template>
      的文件
    </div>
    <div class="el-upload__tip">
      <slot name="tip"></slot>
    </div>
    <el-image-viewer v-if="imgViewVisible" :url-list="[viewImageUrl]" @close="imgViewVisible = false" />
  </div>
</template>

<script setup lang="ts" name="UploadImgs">
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps, UploadFile } from 'element-plus'
import { ElMessage, formContextKey, formItemContextKey } from 'element-plus'
import { listToString } from '@/utils/common'
import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
import { compressAccurately } from 'image-conversion'
import { OssVO } from '@/api/interface/system/oss'
import { globalHeaders } from '@/api'
import { getListByIdsApi, delOssApi } from '@/api/modules/system/oss'
import { ResultData } from '@/api/interface'
interface UploadFileProps {
  modelValue: string | number
  drag?: boolean // 是否支持拖拽上传 ==> 非必传（默认为 true）
  disabled?: boolean // 是否禁用上传组件 ==> 非必传（默认为 false）
  limit?: number // 最大图片上传数 ==> 非必传（默认为 5张）
  fileSize?: number // 图片大小限制 ==> 非必传（默认为 5M）
  fileType?: File.ImageMimeType[] // 图片类型限制 ==> 非必传（默认为 ["image/jpeg", "image/png", "image/gif"]）
  height?: string // 组件高度 ==> 非必传（默认为 150px）
  width?: string // 组件宽度 ==> 非必传（默认为 150px）
  isShowTip?: boolean
  borderRadius?: string // 组件边框圆角 ==> 非必传（默认为 8px）
  compressSupport?: boolean // 是否支持图片压缩 ==> 非必传（默认为 false）
  compressTargetSize?: number // 图片压缩目标大小 ==> 非必传（默认为 300kb）
}

const props = withDefaults(defineProps<UploadFileProps>(), {
  modelValue: () => '',
  drag: true,
  disabled: false,
  limit: 5,
  fileSize: 5,
  isShowTip: true,
  compressSupport: false,
  compressTargetSize: 300,
  fileType: () => ['image/jpeg', 'image/png', 'image/gif'],
  height: '150px',
  width: '150px',
  borderRadius: '8px'
})

const baseUrl = import.meta.env.VITE_API_URL
const uploadImgUrl = ref(baseUrl + '/resource/oss/upload') // 上传的图片服务器地址
const headers = ref(globalHeaders())
// 获取 el-form 组件上下文
const formContext = inject(formContextKey, void 0)
// 获取 el-form-item 组件上下文
const formItemContext = inject(formItemContextKey, void 0)
// 判断是否禁用上传和删除
const self_disabled = computed(() => {
  return props.disabled || formContext?.disabled
})
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
const _fileList = ref<any[]>([])
const uploadList = ref<any[]>([])
const number = ref(0)
const imageUploadRef = ref<ElUploadInstance>()
// 监听 props.modelValue 列表默认值改变
watch(
  () => props.modelValue,
  async (val: string | number) => {
    if (val) {
      // 首先将值转为数组
      let list: OssVO[] = []
      if (Array.isArray(val)) {
        list = val as OssVO[]
      } else {
        const res = await getListByIdsApi(val)
        list = res.data
      }
      // 然后将数组转为对象数组
      _fileList.value = list.map(item => {
        // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
        let itemData
        if (typeof item === 'string') {
          itemData = { name: item, url: item }
        } else {
          // 此处name使用ossId 防止删除出现重名
          itemData = { name: item.ossId, url: item.url, ossId: item.ossId }
        }
        return itemData
      })
    } else {
      _fileList.value = []
      return []
    }
  },
  { deep: true, immediate: true }
)

/**
 * @description 文件上传之前判断
 * @param rawFile 选择的文件
 * */
const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
  const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType)
  if (!imgType) {
    ElMessage.error(`上传图片不符合所需的格式,  请上传${props.fileType.join('/')}图片格式文件!`)
    return false
  }
  if (!imgSize) {
    ElMessage.error('图片大小不能超过 ' + props.fileSize + 'M！')
    return false
  }
  if (props.compressSupport && rawFile.size / 1024 > props.compressTargetSize) {
    return compressAccurately(rawFile, props.compressTargetSize)
  }
  showFullScreenLoading('正在上传图片，请稍候...')
  number.value++
  console.log('number', number.value)
  return imgType && imgSize
}

/**
 * @description 图片上传成功
 * @param response 上传响应结果
 * @param uploadFile 上传的文件
 * */
const emit = defineEmits<{
  'update:modelValue': [value: string]
}>()
const uploadSuccess = (response: ResultData, uploadFile: UploadFile) => {
  if (response.code !== 200) {
    number.value--
    ElMessage.error(response.msg)
    imageUploadRef.value?.handleRemove(uploadFile)
    uploadedSuccessfully()
    return
  }
  uploadList.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
  uploadedSuccessfully()
}

// 上传结束处理
const uploadedSuccessfully = () => {
  if (number.value > 0 && uploadList.value.length === number.value) {
    _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
    uploadList.value = []
    number.value = 0
    emit('update:modelValue', listToString(_fileList.value))
  }
  tryHideFullScreenLoading()
  // 监听表单验证
  formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
}

/**
 * @description 删除图片
 * @param file 删除的文件
 * */
const handleRemove = (file: UploadFile) => {
  const fIndex = _fileList.value.map(f => f.name).indexOf(file.name)
  if (fIndex > -1 && uploadList.value.length === number.value) {
    let ossId = _fileList.value[fIndex].ossId
    delOssApi(ossId)
    _fileList.value.splice(fIndex, 1)
    emit('update:modelValue', listToString(_fileList.value))
    formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
    return false
  }
  return true
}

/**
 * @description 图片上传错误
 * */
const uploadError = () => {
  tryHideFullScreenLoading()
  ElMessage.error('图片上传失败，请您重新上传！')
}

/**
 * @description 文件数超出
 * */
const handleExceed = () => {
  tryHideFullScreenLoading()
  ElMessage.warning(`当前最多只能上传 ${props.limit} 张图片，请移除后上传！`)
}

/**
 * @description 图片预览
 * @param file 预览的文件
 * */
const viewImageUrl = ref('')
const imgViewVisible = ref(false)
const handlePictureCardPreview: UploadProps['onPreview'] = file => {
  viewImageUrl.value = file.url!
  imgViewVisible.value = true
}
</script>

<style scoped lang="scss">
.is-error {
  .upload {
    :deep(.el-upload--picture-card),
    :deep(.el-upload-dragger) {
      border: 1px dashed var(--el-color-danger) !important;
      &:hover {
        border-color: var(--el-color-primary) !important;
      }
    }
  }
}
:deep(.disabled) {
  .el-upload--picture-card,
  .el-upload-dragger {
    cursor: not-allowed;
    background: var(--el-disabled-bg-color) !important;
    border: 1px dashed var(--el-border-color-darker);
    &:hover {
      border-color: var(--el-border-color-darker) !important;
    }
  }
}
.upload-box {
  .no-border {
    :deep(.el-upload--picture-card) {
      border: none !important;
    }
  }
  :deep(.upload) {
    .el-upload-dragger {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 100%;
      height: 100%;
      padding: 0;
      overflow: hidden;
      border: 1px dashed var(--el-border-color-darker);
      border-radius: v-bind(borderRadius);
      &:hover {
        border: 1px dashed var(--el-color-primary);
      }
    }
    .el-upload-dragger.is-dragover {
      background-color: var(--el-color-primary-light-9);
      border: 2px dashed var(--el-color-primary) !important;
    }
    .el-upload-list__item,
    .el-upload--picture-card {
      width: v-bind(width);
      height: v-bind(height);
      background-color: transparent;
      border-radius: v-bind(borderRadius);
    }
    .upload-image {
      width: 100%;
      height: 100%;
      object-fit: contain;
    }
    .upload-handle {
      position: absolute;
      top: 0;
      right: 0;
      box-sizing: border-box;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 100%;
      height: 100%;
      cursor: pointer;
      background: rgb(0 0 0 / 60%);
      opacity: 0;
      transition: var(--el-transition-duration-fast);
      .handle-icon {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 0 6%;
        color: aliceblue;
        .el-icon {
          margin-bottom: 15%;
          font-size: 140%;
        }
        span {
          font-size: 100%;
        }
      }
    }
    .el-upload-list__item {
      &:hover {
        .upload-handle {
          opacity: 1;
        }
      }
    }
    .upload-empty {
      display: flex;
      flex-direction: column;
      align-items: center;
      font-size: 12px;
      line-height: 30px;
      color: var(--el-color-info);
      .el-icon {
        font-size: 28px;
        color: var(--el-text-color-secondary);
      }
    }
  }
  .el-upload__tip {
    line-height: 15px;
  }
}
</style>
